package com.jwgf.Half485.modbusthreadpool;

import com.jwgf.common.Utility;

import org.greenrobot.eventbus.EventBus;

import java.util.HashMap;

public class PLCData {
    private static PLCData data = new PLCData();
    private HashMap<Integer, Boolean> coils = new HashMap<Integer, Boolean>();
    private HashMap<Integer, Boolean> inputs = new HashMap<Integer, Boolean>();
    private HashMap<Integer, Integer> input_registers = new HashMap<Integer, Integer>();
    private HashMap<Integer, Integer> holding_registers = new HashMap<Integer, Integer>();

    private PLCData() {

    }

    public static PLCData getInstance() {
        return data;
    }

    public int getKey(int id, int addr) {
        return (id << 16) | addr;
    }

    public int getKey(HoldingRegister reg) {
        return (reg.id << 16) | reg.addr;
    }

    public boolean getCoil(int id, int addr) {
        int key = getKey(id, addr);
        Boolean value = coils.get(key);
        return value == null ? false : value.booleanValue();
    }

    public void setCoil(int id, int addr, boolean value) {
        if (value != getCoil(id, addr)) {
            coils.put(id << 16 | addr, value);
            Coil coil = new Coil();
            coil.plcData = this;
            coil.id = id;
            coil.addr = addr;
            coil.value = value;
            EventBus.getDefault().post(coil);
        }
    }

    public boolean getInput(int id, int addr) {
        int key = getKey(id, addr);
        Boolean value = inputs.get(key);
        return value == null ? false : value;
    }

    public void setInput(int id, int addr, boolean value) {
        if (value != getInput(id, addr)) {
            inputs.put(id << 16 | addr, value);
            Input input = new Input();
            input.plcData = this;
            input.id = id;
            input.addr = addr;
            input.value = value;
            EventBus.getDefault().post(input);
        }
    }

    public int getInputRegister(int id, int addr) {
        int key = getKey(id, addr);
        Integer value = input_registers.get(key);
        return value == null ? 0 : value;
    }

    public int get2InputRegister(int id, int addr) {
        int keyH = getKey(id, addr);
        int keyL = getKey(id, addr + 1);
        Integer valueH = input_registers.get(keyH);
        Integer valueL = input_registers.get(keyL);
        int h = valueH == null ? 0 : valueH;
        int l = valueL == null ? 0 : valueL;
        return (h << 16) + l;
    }

    public void setInputRegister(int id, int addr, int value) {
        if (value != getInputRegister(id, addr)) {
            input_registers.put(id << 16 | addr, value);
            InputRegister inputRegister = new InputRegister();
            inputRegister.plcData = this;
            inputRegister.id = id;
            inputRegister.addr = addr;
            inputRegister.value = value;
            EventBus.getDefault().post(inputRegister);
        }
    }

    public int getHoldingRegister(int id, int addr) {
        int key = getKey(id, addr);
        Integer value = holding_registers.get(key);
        return value == null ? 0 : value;
    }


    public int get2HoldingRegister(int id, int addr) {
        int keyH = getKey(id, addr);
        int keyL = getKey(id, addr + 1);
        Integer valueH = holding_registers.get(keyH);
        Integer valueL = holding_registers.get(keyL);
        int h = valueH == null ? 0 : valueH;
        int l = valueL == null ? 0 : valueL;
        return (h << 16) + l;
    }

    public void setHoldingRegister(int id, int addr, int value) {
        if (value != getHoldingRegister(id, addr)) {
            HoldingRegister holdingRegister = new HoldingRegister();
            holdingRegister.plcData = this;
            holdingRegister.id = id;
            holdingRegister.addr = addr;
            holdingRegister.value = value;
            holdingRegister.oldValue = getHoldingRegister(id, addr);
            EventBus.getDefault().post(holdingRegister);
            holding_registers.put(id << 16 | addr, value);
        }
    }

    public void readSucceed(ModbusData modbusData) {
        byte id = ModbusUtils.getRequestId(modbusData.send);
        byte cmdType = ModbusUtils.checkCmdType(modbusData.send);
        int startAddr = ModbusUtils.getRequestAddr(modbusData.send);
        byte[] data = ModbusUtils.getReturnData(modbusData.recv);

        switch (cmdType) {
            case ModbusUtils.READ_COIL: {
                int addr = startAddr;
                for (int i = 0; i < data.length; ++i) {
                    for (int j = 0; j < 8; ++j) {
                        int value = ((data[i] & 0xff) >> j) & 1;
                        boolean bool = value == 1 ? true : false;
                        setCoil(id, addr, bool);
                        ++addr;
                    }
                }
            }
            break;
            case ModbusUtils.READ_INPUT: {
                int addr = startAddr;
                for (int i = 0; i < data.length; ++i) {
                    for (int j = 0; j < 8; ++j) {
                        int value = ((data[i] & 0xff) >> j) & 1;
                        boolean bool = value == 1 ? true : false;
                        setInput(id, addr, bool);
                        ++addr;
                    }
                }
            }
            break;
            case ModbusUtils.READ_INPUT_REGISTER: {
                int addr = startAddr;
                for (int i = 0; i < data.length; i += 2) {
                    byte[] b = {data[i], data[i + 1]};
                    int value = Utility.ByteArrayUtils.byteArray2Short(b) & 0xffff;
                    setInputRegister(id, addr, value);
                    ++addr;
                }
            }
            break;
            case ModbusUtils.READ_HOLDING_REGISTER: {
                int addr = startAddr;
                for (int i = 0; i < data.length; i += 2) {
                    byte[] b = {data[i], data[i + 1]};
                    int value = Utility.ByteArrayUtils.byteArray2Short(b) & 0xffff;
                    setHoldingRegister(id, addr, value);
                    ++addr;
                }
            }
            break;
        }
        ModbusResult modbusResult = new ModbusResult();
        modbusResult.plcData = this;
        modbusResult.id = ModbusUtils.getRequestId(modbusData.send);
        modbusResult.cmd = ModbusUtils.checkCmdType(modbusData.send);
        modbusResult.addr = ModbusUtils.getRequestAddr(modbusData.send);
        EventBus.getDefault().post(modbusResult);
    }

    public void writeSucceed(ModbusData modbusData) {
        byte id = ModbusUtils.getRequestId(modbusData.send);
        byte cmdType = ModbusUtils.checkCmdType(modbusData.send);
        int startAddr = ModbusUtils.getRequestAddr(modbusData.send);
        byte[] data = ModbusUtils.getRequestData(modbusData.send);

        switch (cmdType) {
            case ModbusUtils.WRITE_COIL: {
                int addr = startAddr;
                for (int i = 0; i < data.length; ++i) {
                    for (int j = 0; j < 8; ++j) {
                        int value = ((data[i] & 0xff) >> j) & 1;
                        boolean bool = value == 1 ? true : false;
                        setCoil(id, addr, bool);
                        ++addr;
                    }
                }
            }
            break;
            case ModbusUtils.WRITE_HOLDING_REGISTER: {
                int addr = startAddr;
                for (int i = 0; i < data.length; i += 2) {
                    byte[] b = {data[i], data[i + 1]};
                    int value = Utility.ByteArrayUtils.byteArray2Short(b) & 0xffff;
                    setHoldingRegister(id, addr, value);
                    ++addr;
                }
            }
            break;
        }
        ModbusResult modbusResult = new ModbusResult();
        modbusResult.plcData = this;
        modbusResult.id = ModbusUtils.getRequestId(modbusData.send);
        modbusResult.cmd = ModbusUtils.checkCmdType(modbusData.send);
        modbusResult.addr = ModbusUtils.getRequestAddr(modbusData.send);
        EventBus.getDefault().post(modbusResult);

    }

    public void readFailed(ModbusData modbusData) {
        ModbusResult modbusFailed = new ModbusResult();
        modbusFailed.plcData = this;
        modbusFailed.id = ModbusUtils.getRequestId(modbusData.send);
        modbusFailed.cmd = ModbusUtils.checkCmdType(modbusData.send);
        modbusFailed.addr = ModbusUtils.getRequestAddr(modbusData.send);
        EventBus.getDefault().post(modbusFailed);
    }

    public void writeFailed(ModbusData modbusData) {
        byte id = ModbusUtils.getRequestId(modbusData.send);
        byte cmdType = ModbusUtils.checkCmdType(modbusData.send);
        int startAddr = ModbusUtils.getRequestAddr(modbusData.send);
        byte[] data = ModbusUtils.getRequestData(modbusData.send);
        switch (cmdType) {
            case ModbusUtils.WRITE_COIL: {
                int addr = startAddr;
                int len = ModbusUtils.getRequestDataLength(modbusData.send);
                for (int i = 0; i < len; ++i) {
                    boolean bit = Utility.BitUtils.getBitValue(data[i / 8], i % 8);
                    if (bit != getCoil(id, startAddr)) {
                        Coil coil = new Coil();
                        coil.plcData = this;
                        coil.id = id;
                        coil.addr = addr;
                        coil.value = getCoil(id, addr);
                        EventBus.getDefault().post(coil);
                    }
                    ++addr;
                }

                break;
            }
            case ModbusUtils.WRITE_HOLDING_REGISTER: {
                int addr = startAddr;
                for (int i = 0; i < data.length; i = i + 2) {
                    byte[] b = {data[i], data[i + 1]};
                    int value = Utility.ByteArrayUtils.byteArray2Short(b) & 0xffff;
                    if (value != getHoldingRegister(id, addr)) {
                        HoldingRegister holdingRegister = new HoldingRegister();
                        holdingRegister.plcData = this;
                        holdingRegister.id = id;
                        holdingRegister.addr = addr;
                        holdingRegister.value = getHoldingRegister(id, addr);
                        EventBus.getDefault().post(holdingRegister);
                    }
                    ++addr;
                }
                break;
            }
            default:
                break;
        }
        ModbusResult modbusFailed = new ModbusResult();
        modbusFailed.plcData = this;
        modbusFailed.id = ModbusUtils.getRequestId(modbusData.send);
        modbusFailed.cmd = ModbusUtils.checkCmdType(modbusData.send);
        modbusFailed.addr = ModbusUtils.getRequestAddr(modbusData.send);
        EventBus.getDefault().post(modbusFailed);

    }

    public static class Coil {
        public PLCData plcData;
        public int id;
        public int addr;
        public boolean value;
    }

    public static class Input {
        public PLCData plcData;
        public int id;
        public int addr;
        public boolean value;
    }

    public static class InputRegister {
        public PLCData plcData;
        public int id;
        public int addr;
        public int value;
    }

    public static class HoldingRegister {
        public PLCData plcData;
        public int id;
        public int addr;
        public int value;
        public int oldValue;
    }

    public static class ModbusResult {
        public PLCData plcData;
        public int id;
        public int cmd;
        public int addr;
    }

}
